package com.example.j2v8demo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import com.eclipsesource.v8.JavaCallback;
import com.eclipsesource.v8.JavaVoidCallback;
import com.eclipsesource.v8.Releasable;
import com.eclipsesource.v8.V8;
import com.eclipsesource.v8.V8Array;
import com.eclipsesource.v8.V8Function;
import com.eclipsesource.v8.V8Object;
import com.eclipsesource.v8.utils.MemoryManager;

public class MainActivity extends AppCompatActivity {

    private String TAG = "huangxiaoguo";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.e(TAG, "*******************************使用Integer***********************************");
        printV8Integer();

        Log.e(TAG, "********************************使用String**********************************");
        printV8String();

        Log.e(TAG, "********************************获取Javascript对象**********************************");
        getJsObject();

        Log.e(TAG, "********************************数组**********************************");
        printArray();

        Log.e(TAG, "********************************java调用JavaScript函数（一个参数）**********************************");
        exeJsFunction();


        Log.e(TAG, "********************************java调用JavaScript函数（多个参数）**********************************");
        exeJsFunctionBase();


        Log.e(TAG, "********************************java调用JavaScript函数（V8Function调用call）**********************************");
        exeJsFunctionBaseCall();

        Log.e(TAG, "********************************Js 调用 Java function（注册 Java 回调 —— 接口方式）**********************************");
        JsCallJavaViaInterface();

        Log.e(TAG, "********************************Js 调用 Java function（反射）**********************************");
        jsCallJavaViaReflection();

        Log.e(TAG, "********************************Js 调用 Java function（反射2）**********************************");
        jsCallJavaViaReflection2();

        Log.e(TAG, "********************************Js 调用 Java function（无需注册将 js 执行 java 函数）**********************************");
        jsCallJavaFunctionViaFunction();
    }

    /**
     * 在 j2v8 V4 中，增加了新的方式，可以无需注册将 js 执行 java 函数。
     * <p>
     * 在之前的例子中，java 函数必须先注册到 js 才可以被调用。
     * 试想这样一个需求：js 有一个函数，需要传入一个回调函数。
     * 此时希望 java 来执行此函数，那么参数应该怎么传递呢？之前，是没有办法，但是现在有了。
     */
    private void jsCallJavaFunctionViaFunction() {
        V8 runtime = V8.createV8Runtime();
        runtime.executeVoidScript(""
                + "function add(a, b, callback){\n"
                + "    callback(a + b)\n"
                + "}");
        V8Function function = new V8Function(runtime, new JavaCallback() {
            @Override
            public Object invoke(V8Object v8Object, V8Array v8Array) {
                Log.e(TAG,  v8Array.getInteger(0)+"");
                return null;
            }
        });
        V8Array push = new V8Array(runtime).push(10).push(20).push(function);
        runtime.executeVoidFunction("add",push);
        function.close();
        push.close();
        runtime.close();
    }


    public void getJsText(String text){
        Log.e(TAG, "[TEXT] " + text);
    }

    /**
     * Js 调用 Java function
     * 2.反射
     * <p>
     * java 函数必须先注册到 js 才可以被调用
     * <p>
     * 在本例中，通过反射注册了现有 java 对象的方法。必须指定Java对象、方法的名称和参数列表。
     * 并且此处不是直接注册为全局函数，而是先创建了一个名为 console 的 js 对象，把函数注册到了此对象上，
     * <p>
     * 不难发现，通过接口方式注册，参数可以是动态的
     * 而通过反射注册，参数必须明确指定并且与 java 参数严格匹配，若参数不匹配则会异常。
     */
    private void jsCallJavaViaReflection2() {
        V8 runtime = V8.createV8Runtime();
        //自动内存管理
        MemoryManager scope = new MemoryManager(runtime); // 实例化 MemoryManager

        V8Object v8Object = new V8Object(runtime);
        runtime.add("jsMethod",v8Object);

        //参数1：context(当前Activity对象)   参数2：java方法名，参数三：js里面写的方法名  参数4：方法的参数类型 个数
        v8Object.registerJavaMethod(this,"getJsText","setText",new Class<?>[]{String.class});
        runtime.executeScript("jsMethod.setText('我是JS方法');");

        //在用了自动内存管理后，代码会变得非常简洁
        scope.release();
    }


    private  class AndroidConsole {
        public void log( String message) {
            Log.e(TAG, "[INFO] " + message);
        }

        public void error( Integer message) {
            Log.e(TAG, "[ERROR] " + message);
        }
    }
    /**
     * Js 调用 Java function
     * 2.反射
     * <p>
     * java 函数必须先注册到 js 才可以被调用
     * <p>
     * 在本例中，通过反射注册了现有 java 对象的方法。必须指定Java对象、方法的名称和参数列表。
     * 并且此处不是直接注册为全局函数，而是先创建了一个名为 console 的 js 对象，把函数注册到了此对象上，
     * <p>
     * 不难发现，通过接口方式注册，参数可以是动态的
     * 而通过反射注册，参数必须明确指定并且与 java 参数严格匹配，若参数不匹配则会异常。
     */
    private void jsCallJavaViaReflection() {
        V8 runtime = V8.createV8Runtime();

        AndroidConsole androidConsole = new AndroidConsole();

        V8Object v8Object = new V8Object(runtime);
        runtime.add("console",v8Object);

        //参数1：对象索引(这里是AndroidConsole对象)  参数2：java方法名，参数三：js里面写的方法名  参数4：方法的参数类型 个数
        v8Object.registerJavaMethod(androidConsole,"log","jlog",new Class<?>[]{String.class});
        v8Object.registerJavaMethod(androidConsole,"error","jerr",new Class<?>[]{Integer.class});
        // 然后可以直接在 js 中调用 `console.jlog('hello, world')` 与 `console.jerr('hello, world')` 了。
        runtime.executeScript("console.jlog('hello, world');");
        runtime.executeScript("console.jerr(100);");

        v8Object.close();
        runtime.close();
    }


    /**
     *  要注册函数到 js，首先要创建一个类，并实现 JavaCallback 接口（如果 java 函数没有返回值，则实现 JavaVoidCallback 接口即可）。
     *  这两个接口均有一个 invoke(V8Object receiver, V8Array parameters) 函数，
     *  当被 js 调用时就会触发，这样就可执行 java 代码了
     */
    private JavaVoidCallback javaVoidCallback= new JavaVoidCallback() {
        /**
         *  receiver 是此函数被调用时所基于的对象
         *  parameters 是传入的参数列表，表现为一个 V8数组，可以从中提取 js 传入的各个参数。
         *  在这个例子中，print 不再基于 Global，而是基于 array[i] 被调用的，
         *  因此 array[i] 将被传入 java 作为 receiver. 最终 java 将依次输出 AA says Hi., BB says Hi., CC says Hi..
         *
         * @param receiver
         * @param parameters
         */
        @Override
        public void invoke(V8Object receiver, V8Array parameters) {
            Log.e(TAG,receiver.getString("first")+parameters.get(0)+" "+parameters.get(1));
            // 必须释放从参数列表中检索到的所有V8Object，因为它们是由于方法调用而返回给您的。
            for (int i = 0; i < parameters.length(); i++) {
                Object o = parameters.get(i);
                if (o instanceof Readable){
                    ((Releasable) o).close();
                }
            }
            receiver.close();
            parameters.close();
        }
    };
    /**
     * Js 调用 Java function（注册 Java 回调）
     * 1.接口方式
     * <p>
     * java 函数必须先注册到 js 才可以被调用
     */
    private void JsCallJavaViaInterface() {
        V8 runtime = V8.createV8Runtime();
        runtime.registerJavaMethod(javaVoidCallback,"print");
        runtime.executeVoidScript("" +
                "var array = [{first:'AA'}, {first:'BB'}, {first:'CC'}];\n" +
                "for ( var i = 0; i < array.length; i++ ) {\n" +
                "  print.call(array[i], \" says Hi.\",\"Second\");" +
                "}");
        runtime.close();
    }


    /**
     * java调用JavaScript函数（V8Function调用call）
     * 在JS中万物皆对象，函数也不例外。在 j2v8 中，一切 js 对象都用 V8Object 表示，
     * 我们可以直接将其强制转换为 V8Function。
     * <p>
     * V8Function 表示的就是一个 js 函数对象，它拥有 call() 方法可以直接被调用。
     */
    private void exeJsFunctionBaseCall() {
        V8 runtime = V8.createV8Runtime();
        runtime.executeVoidScript("" +
                "function add(a,b){" +
                "   return a + b" +
                "}");
        //先判断 add是不是一个函数
        if (runtime.getType("add")==V8.V8_FUNCTION){
            V8Array push = new V8Array(runtime).push(25).push(50);
            Object add = runtime.getObject("add");
            if (add instanceof V8Function){
                V8Function addFunction = (V8Function) add;
                int addResult = (int) addFunction.call(null, push);
                Log.e(TAG, "JS result addResult = "+addResult);
                push.close();
                addFunction.close();
            }
        }
        runtime.close();

    }

    /**
     * java调用JavaScript函数（多个参数）
     */
    private void exeJsFunctionBase() {
        V8 runtime = V8.createV8Runtime();
        runtime.executeVoidScript("" +
                "function add(a,b){" +
                "   return a + b" +
                "}");
        V8Array v8Array = new V8Array(runtime).push(10).push(40);
        int add = runtime.executeIntegerFunction("add", v8Array);
        Log.e(TAG, "JS result add = "+add);
        v8Array.close();
        runtime.close();
    }


    /**
     * 除了执行脚本外，Java还可使用J2V8调用JavaScript函数。
     * 函数可以是全局函数，也可以附加到另一个Object，并且可以选择返回结果。
     */
    private void exeJsFunction() {
        V8 runtime = V8.createV8Runtime();
        runtime.executeVoidScript(""
                + "var hockeyTeam = {\n"
                + "name      : 'WolfPack',\n"
                + "players   : [],\n"
                + "addPlayer : function(player) {\n"
                + "              this.players.push(player);\n"
                + "              return this.players.length;\n"
                + "}\n"
                + "}\n");
        V8Object hockeyTeam = runtime.getObject("hockeyTeam");
        V8Object player1 = new V8Object(runtime).add("name", "John");
        V8Object player2 = new V8Object(runtime).add("name", "Huang");
        //获取palyers数组
        V8Array players = hockeyTeam.getArray("players");
        players.push(player1).push(player2);

        //创建对象为addPlayer方法传值
        V8Object player3 = new V8Object(runtime).add("name", "Add");
        //注意：将值封装到V8Array中传给addPlayer方法（只有一个参数，因此push一个值）
        V8Array push = new V8Array(runtime).push(player3);

        int size = hockeyTeam.executeIntegerFunction("addPlayer", push);

        //JS result size = 3
        Log.e(TAG, "JS result size = "+size);
        players.close();
        player1.close();
        player2.close();
        player3.close();
        push.close();
        hockeyTeam.release();
        runtime.close();
    }

     /**
     * 为js添加数组&获取js数组
     */
    private void printArray() {
        V8 runtime = V8.createV8Runtime();
        runtime.executeVoidScript("" +
                "var hockeyTeam = {name : 'WolfPack'};\n");
        V8Object hockeyTeam = runtime.getObject("hockeyTeam");
        V8Object player1 = new V8Object(runtime).add("name", "Huang");
        V8Object player2 = new V8Object(runtime).add("name", "Ji");
        V8Array players = new V8Array(runtime).push(player1).push(player2);
        hockeyTeam.add("players",players);
        player1.release();
        player2.release();
        players.close();
        for (String key : hockeyTeam.getKeys()) {
            Object value = hockeyTeam.get(key);
            Log.e(TAG, "key:" + key + ",value:" + value);
            if (key.equals("players")){
                V8Array array = (V8Array) value;
                for (int i = 0; i < array.length(); i++) {
                    V8Object obj= (V8Object) array.get(i);
                    Log.e(TAG, "name:" + obj.getString("name"));
                }
            }
        }
    }

    /**
     * 获取Javascript对象
     * 使用J2V8可以从Java中获取javascript对象的句柄
     * <p>
     * V8Object还提供了一些其他有用的方法。
     * getKeys（）将返回与Object关联的键。
     * getType（String key）将返回与键关联的对象的类型。
     * 使用这两种方法，可以动态遍历复杂的对象图。
     */
    private void getJsObject() {
        V8 runtime = V8.createV8Runtime();
        runtime.executeVoidScript("" +
                "var person = {};\n" +
                "var student = {};\n" +
                "var hockeyTeam = {name : 'WolfPack'};\n" +
                "person.first = 'Ian';\n" +
                "person['name'] = 'Huang';\n" +
                "person['last'] = 'Bull';\n" +
                "person.hockeyTeam = hockeyTeam;\n");

        V8Object person = runtime.getObject("person");
        V8Object student = runtime.getObject("student");
        V8Object hockeyTeam = person.getObject("hockeyTeam");

        //JS hockeyTeam name = WolfPack
        Log.e(TAG," JS hockeyTeam name = "+hockeyTeam.getString("name"));
        //JS person name = Bull
        Log.e(TAG," JS person name = "+person.getString("last"));

        /**
         * 为hockeyTeam对象添加一个key属性，值为：huangxiaoaguo
         */
        hockeyTeam.add("key","huangxiaoaguo");
        //JS hockeyTeam key = huangxiaoaguo
        Log.e(TAG," JS hockeyTeam key = "+hockeyTeam.getString("key"));

        hockeyTeam.add("student", student);
        Log.e(TAG," JS hockeyTeam  student "+runtime.executeBooleanScript("student === hockeyTeam.student"));

        person.close();
        hockeyTeam.close();
        student.close();
        runtime.release(false);

    }

    /**
     * 使用String(返回最后一行结果)
     */
    private void printV8String() {
        V8 runtime = V8.createV8Runtime();
        String result = runtime.executeStringScript("" +
                "var hello = 'hello, ';\n" +
                "var world = 'world!';\n" +
                "hello.concat(world);\n");
        Log.e(TAG, "JS result = " + result);//S result = hello, world!
        runtime.release(true);
    }

    /**
     * 使用Integer(返回最后一行Integer结果)
     * 两个字符串连接起来并且返回了结果字符串的长度
     */
    private void printV8Integer() {
        V8 runtime = V8.createV8Runtime();
        int result = runtime.executeIntegerScript("" +
                "var hello = 'hello, ';\n" +
                "var world = 'world!';\n" +
                "hello.concat(world).length;\n");
        Log.e(TAG, "JS result = " + result);//JS result = 13
        runtime.release(true);

    }
}
